﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

namespace HttpHandlerSpace
{
	/// <summary>
	/// Does basic Http and Html handling - including Cookie handling!
	/// </summary>
	public class HttpHandler
	{
		#region Delegate
		/// <summary>
		/// Delegate that represents a function to call after request is complete
		/// </summary>
		/// <param name="response">Response that is returned by request</param>
		public delegate void AsyncResponseCallback(HttpWebResponse response);
		#endregion
		#region Fields
		private CookieCollection cookieJar;
		private Exception lastException;
		private int? timeout;
		#endregion
		#region Constructors
		/// <summary>
		/// Default Constructor
		/// </summary>
		public HttpHandler()
		{
			cookieJar = new CookieCollection();
			lastException = null;
			timeout = null;
		}
		#endregion
		#region Properties
		/// <summary>
		/// Gets the last returned Exception
		/// </summary>
		public Exception LastException
		{
			get
			{ return lastException; }
		}

		/// <summary>
		/// The self-managed cookies
		/// </summary>
		public CookieCollection Cookies
		{
			get
			{ return cookieJar; }
		}

		/// <summary>
		/// The number of milliseconds to wait before the request times out. Settings to null uses the HttpWebRequest default (100 seconds)
		/// </summary>
		public int? Timeout
		{
			get
			{ return timeout; }
			set
			{ timeout = value; }
		}
		#endregion
		#region Public Methods
		/// <summary>
		/// Creates a WebRequest to a given URL with self-managed cookies and no POST vars
		/// </summary>
		/// <param name="url">URL to request</param>
		/// <returns>A HTTPWebRequest for given URL with self-managed cookies and no POST vars</returns>
		public HttpWebRequest nextRequest(Uri url)
		{
			return makeRequest(url, new Dictionary<string, string>(), cookieJar, timeout);
		}

		/// <summary>
		/// Creates a WebRequest to a given URL with POST vars and self-managed cookies
		/// </summary>
		/// <param name="url">URL to request</param>
		/// <param name="postVars">POST vars to pass</param>
		/// <returns>A HTTPWebRequest for given URL with POST vars and self-managed cookies</returns>
		public HttpWebRequest nextRequest(Uri url, Dictionary<string, string> postVars)
		{
			return makeRequest(url, postVars, cookieJar, timeout);
		}

		/// <summary>
		/// Creates a WebRequest to a given URL with POST vars and self-managed cookies
		/// </summary>
		/// <param name="url">URL to request</param>
		/// <param name="postString">POST vars to pass</param>
		/// <returns>A HTTPWebRequest for given URL with POST vars and self-managed cookies</returns>
		public HttpWebRequest nextRequest(Uri url, string postString)
		{
			return makeRequest(url, postString, cookieJar, timeout);
		}

		/// <summary>
		/// Syncronous web call - manages cookies; throws errors
		/// </summary>
		/// <param name="requestToGet">Request to process</param>
		/// <returns>A WebResponse from the call</returns>
		public HttpWebResponse getResponse(HttpWebRequest requestToGet)
		{
			HttpWebResponse returnedResponse = (HttpWebResponse)requestToGet.GetResponse();
			cookieJar.Add(returnedResponse.Cookies);

			return returnedResponse;
		}

		/// <summary>
		/// Fully managed async web call - managed cookies and errors (return via events)
		/// </summary>
		/// <param name="requestToGet">Request to process</param>
		/// <param name="callback">Function to call when request is complete</param>
		public void getResponseAsyncStart(HttpWebRequest requestToGet, AsyncResponseCallback callback)
		{
			AsyncResponseState responseState = new AsyncResponseState() { callback = callback, request = requestToGet };
			requestToGet.BeginGetResponse(new AsyncCallback(ResponseCallback), responseState);
		}

		private void ResponseCallback(IAsyncResult result)
		{
			AsyncResponseState responseState = (AsyncResponseState)result.AsyncState;
			HttpWebResponse response = null;

			try
			{
				response = (HttpWebResponse)responseState.request.EndGetResponse(result);
			}
			catch (SystemException ex)
			{
				lastException = ex;
			}

			if (responseState.callback != null)
			{
				responseState.callback.Invoke(response);
			}
		}

		/// <summary>
		/// Clears out all self-managed cookies
		/// </summary>
		public void clearCookies()
		{
			cookieJar = null;
			cookieJar = new CookieCollection();
		}
		#endregion
		#region Static Methods
		/// <summary>
		/// Creates a HttpWebRequest given a URL and makes the request use GET (instead of POST)
		/// </summary>
		/// <param name="url">URL to retrieve</param>
		/// <returns>A HttpWebRequest to the URL; with the request using GET</returns>
		public static HttpWebRequest makeGetRequest(Uri url)
		{
			HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(url);
			myRequest.Method = "GET";

			return myRequest;
		}

		/// <summary>
		/// Creates a HttpWebRequest given a URL (with no cookies or POST vars)
		/// </summary>
		/// <param name="url">URL to request</param>
		/// <returns>A HTTPWebRequest to the URL; with no cookies or POST vars)</returns>
		public static HttpWebRequest makeRequest(Uri url)
		{
			return makeRequest(url, new Dictionary<string, string>(), new CookieCollection(), null);
		}

		/// <summary>
		/// Creates a HttpWebRequest given a URL and POST vars (with no cookies)
		/// </summary>
		/// <param name="url">URL to request</param>
		/// <param name="postVars">POST vars to pass with the request</param>
		/// <returns>A HTTPWebRequest to the URL, with POST vars attached; but no cookies</returns>
		public static HttpWebRequest makeRequest(Uri url, Dictionary<string, string> postVars)
		{
			return makeRequest(url, postVars, new CookieCollection(), null);
		}

		/// <summary>
		/// Creates a HttpWebRequest given the url, post variables and cookies
		/// </summary>
		/// <param name="url">URL to request</param>
		/// <param name="postVars">POST variable to pass in request</param>
		/// <param name="cookies">Cookies to pass in the Request</param>
		/// <returns>A HTTPWebRequest to the URL, with POST vars and Cookies attached</returns>
		public static HttpWebRequest makeRequest(Uri url, Dictionary<string, string> postVars, CookieCollection cookies)
		{
			return makeRequest(url, postVars, cookies, null);
		}

		/// <summary>
		/// Creates a HttpWebRequest given the url, post variables and cookies
		/// </summary>
		/// <param name="url">URL to request</param>
		/// <param name="postVars">POST variable to pass in request</param>
		/// <param name="cookies">Cookies to pass in the Request</param>
		/// <param name="timeout">Milliseconds to wait before the response times out. Set to null to use the default (100 seconds)</param>
		/// <returns>A HTTPWebRequest to the URL, with POST vars and Cookies attached</returns>
		public static HttpWebRequest makeRequest(Uri url, Dictionary<string, string> postVars, CookieCollection cookies, int? timeout)
		{
			string postString = "";

			foreach (string key in postVars.Keys)
			{
				postString += "&" + key + "=" + postVars[key];
			}
			if (postString.Length > 0)
				postString = postString.Substring(1);

			return makeRequest(url, postString, cookies, timeout);
		}

		/// <summary>
		/// Creates a HttpWebRequest given the url, post variables and cookies
		/// </summary>
		/// <param name="url">URL to request</param>
		/// <param name="postString">The post string to submit</param>
		/// <param name="cookies">Cookies to pass in the Request</param>
		/// <param name="timeout">Milliseconds to wait before the response times out. Set to null to use the default (100 seconds)</param>
		/// <returns>A HTTPWebRequest to the URL, with POST vars and Cookies attached</returns>
		public static HttpWebRequest makeRequest(Uri url, string postString, CookieCollection cookies, int? timeout)
		{
			HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(url);
			UTF8Encoding encoder = new UTF8Encoding();
			myRequest.Method = "POST";

			myRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; bgft)";
			myRequest.CookieContainer = new CookieContainer();
			myRequest.CookieContainer.Add(url, cookies);
			myRequest.Method = "POST";
			myRequest.ContentType = "application/x-www-form-urlencoded";
			myRequest.KeepAlive = true;
			myRequest.Timeout = timeout ?? 100000;

			byte[] postBytes = encoder.GetBytes(postString);

			myRequest.ContentLength = postBytes.Length;

			using (StreamWriter sw = new StreamWriter(myRequest.GetRequestStream()))
			{
				sw.Write(postString);
				sw.Flush();
			}

			return myRequest;
		}

		/// <summary>
		/// Extracts the Response from a HttpWebResponse
		/// </summary>
		/// <param name="inResponse">HttpWebResponse to extract data from</param>
		/// <returns>The response stream, as a string, from the given response</returns>
		public static string responseToString(WebResponse inResponse)
		{
			string responseString = string.Empty;

			using (StreamReader sr = new StreamReader(inResponse.GetResponseStream()))
			{
				responseString = sr.ReadToEnd();
			}

			return responseString;
		}
		#endregion
	}

	/// <summary>
	/// Holds the values that are needed after an async request
	/// </summary>
	internal struct AsyncResponseState
	{
		/// <summary>
		/// The function to call if the request is successfull
		/// </summary>
		public HttpHandler.AsyncResponseCallback callback;
		/// <summary>
		/// The request that was made
		/// </summary>
		public HttpWebRequest request;
	}
}
